﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2024 LiLong                                                   **
**  This file is part of GnssLogs.                                              **
**                                                                              **
**  GnssLogs is free software: you can redistribute it and/or modify            **
**  it under the terms of the GNU Lesser General Public License as published by **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  GnssLogs is distributed in the hope that it will be useful,                 **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU Lesser General Public License for more details.                         **
**                                                                              **
**  You should have received a copy of the GNU Lesser General Public License    **
**  along with GnssLogs.  If not, see <https://www.gnu.org/licenses/>.          **
**********************************************************************************/
#pragma once

#include <tao/pegtl.hpp>
#include <tao/pegtl/contrib/unescape.hpp>

namespace GnssLogs
{
inline namespace Commons
{
using namespace tao::pegtl;

#define NMEA_TALKER_IDENTIFIER(sentence)                                                                                                   \
    sor<TAO_PEGTL_STRING("$AI"##sentence /*Alarm Indicator, (AIS?)*/),                                                                     \
        TAO_PEGTL_STRING("$AP"##sentence /*Auto Pilot (pypilot?)*/),                                                                       \
        TAO_PEGTL_STRING("$BD"##sentence /*BeiDou (China)*/),                                                                              \
        TAO_PEGTL_STRING("$CD"##sentence /*Digital Selective Calling (DSC)*/),                                                             \
        TAO_PEGTL_STRING("$EC"##sentence /*Electronic Chart Display & Information System (ECDIS) */),                                      \
        TAO_PEGTL_STRING("$GA"##sentence /*Galileo Positioning System*/),                                                                  \
        TAO_PEGTL_STRING("$GB"##sentence /*BeiDou (China)*/),                                                                              \
        TAO_PEGTL_STRING("$GI"##sentence /*NavIC, IRNSS (India)*/),                                                                        \
        TAO_PEGTL_STRING("$GL"##sentence /*GLONASS, according to IEIC 61162-1*/),                                                          \
        TAO_PEGTL_STRING("$GN"##sentence /*Combination of multiple satellite systems (NMEA 1083)*/),                                       \
        TAO_PEGTL_STRING("$GP"##sentence /*Global Positioning System receiver*/),                                                          \
        TAO_PEGTL_STRING("$GQ"##sentence /*QZSS regional GPS augmentation system (Japan)*/),                                               \
        TAO_PEGTL_STRING("$HC"##sentence /*Heading/Compass*/),                                                                             \
        TAO_PEGTL_STRING("$HE"##sentence /*Gyro, north seeking*/),                                                                         \
        TAO_PEGTL_STRING("$II"##sentence /*Integrated Instrumentation*/),                                                                  \
        TAO_PEGTL_STRING("$IN"##sentence /*Integrated Navigation*/),                                                                       \
        TAO_PEGTL_STRING("$LC"##sentence /*Loran-C receiver (obsolete)*/),                                                                 \
        TAO_PEGTL_STRING("$PQ"##sentence /*QZSS (Quectel Quirk)*/),                                                                        \
        TAO_PEGTL_STRING("$QZ"##sentence /*QZSS regional GPS augmentation system (Japan)*/),                                               \
        TAO_PEGTL_STRING("$SD"##sentence /*Depth Sounder*/),                                                                               \
        TAO_PEGTL_STRING("$ST"##sentence /*Skytraq*/),                                                                                     \
        TAO_PEGTL_STRING("$TI"##sentence /*Turn Indicator*/),                                                                              \
        TAO_PEGTL_STRING("$YX"##sentence /*Transducer*/),                                                                                  \
        TAO_PEGTL_STRING("$WI"##sentence /*Weather Instrument*/)>

// clang-format off
struct PlusMinus : opt<one<'+', '-'>> {};
struct Dot : one<'.'> {};
struct E : one<'e', 'E'> {};
struct Exponent : seq<opt<one<'+', '-'>>, plus<digit>> {};
struct Floating : seq<opt<PlusMinus>, if_then_else<Dot, plus<digit>, seq<plus<digit>, opt<Dot, star<digit>>>>, opt<E, Exponent>> {};
struct CheckSum : seq<xdigit, xdigit> {};
using Split      = one<','>;
using Tail = opt<one<'\r'>, one<'\n'>>;

struct Utchh : seq<digit, digit> {};
struct Utcmm : seq<digit, digit> {};
struct Utcss : seq<digit, digit> {};
struct UtcssLast : seq<digit, digit, opt<digit>> {};
using Utc = seq<Utchh, Utcmm, Utcss, one<'.'>, UtcssLast>;
struct SystemId : xdigit {};
struct SignalId : xdigit {};

struct CRC32 : rep<8, xdigit> {};

struct PositioningSystemModeIndicator : one<'A', 'D', 'E', 'M', 'N'> {};

struct Datedd : seq<digit, digit> {};
struct Datemm : seq<digit, digit> {};
struct Dateyy : seq<digit, digit> {};
using DateExpression = seq<Datedd, Datemm, Dateyy>;

struct EscapedX : seq<one<'x'>, rep<2, xdigit>> {};
struct Escapedu : seq<one<'u'>, rep<4, xdigit>> {};
struct EscapedU : seq<one<'U'>, rep<8, xdigit>> {};
struct EscapedC : one<'\'', '"', '?', '\\', 'a', 'b', 'f', 'n', 'r', 't', 'v'> {};
struct Escaped : sor<EscapedX, Escapedu, EscapedU, EscapedC> {};
struct Character : if_then_else<one<'\\'>, Escaped, utf8::range<0x20, 0x10FFFF>> {};
struct Literal : seq<one<'"'>, until<one<'"'>, Character>> {};
struct StringLiteral : seq<pad<Literal, blank>> {};

template<typename T> struct StringAction {};
template<> struct StringAction<utf8::range<0x20, 0x10FFFF>> : unescape::append_all {};
template<> struct StringAction<EscapedX> : unescape::unescape_x {};
template<> struct StringAction<Escapedu> : unescape::unescape_u {};
template<> struct StringAction<EscapedU> : unescape::unescape_u {};
template<> struct StringAction<EscapedC> : unescape::unescape_c<EscapedC, '\'', '"', '?', '\\', '\a', '\b', '\f', '\n', '\r', '\t', '\v'> {};

struct Bool : sor<TAO_PEGTL_STRING("TRUE"), TAO_PEGTL_STRING("FALSE")> {};
// clang-format on

template<>
struct StringAction<StringLiteral>
{
    template<typename Rule,
             apply_mode A,
             rewind_mode M,
             template<typename...>
             class Action,
             template<typename...>
             class Control,
             typename ParseInput,
             typename... States>
    static bool match(ParseInput& in, std::string& string, States&&... st)
    {
        try
        {
            return tao::pegtl::match<Rule, A, M, Action, Control>(in, string);
        }
        catch (...)
        {
            return false;
        }
    }
};

template<typename Subclass>
struct CRTP
{
    template<typename Data, typename... Args>
    static inline void setValue(Data& data, Args&&... args)
    {
        Subclass::setValue(data, std::forward<Args&&>(args)...);
    }
};

template<typename Subclass, typename Int, int base = 10>
struct IntHelper : CRTP<Subclass>
{
    template<typename ParseInput, typename Data, typename... Args>
    static bool apply(ParseInput& in, Data& data, Args&... args)
    {
        CRTP<Subclass>::setValue(
            data, static_cast<Int>(std::stoll(std::string(in.begin(), in.size()), nullptr, base)), std::forward<Args&>(args)...);
        return true;
    }
};

template<typename Subclass>
struct DoubleHelper : CRTP<Subclass>
{
    template<typename ParseInput, typename Data, typename... Args>
    static bool apply(ParseInput& in, Data& data, Args&... args)
    {
        CRTP<Subclass>::setValue(data, std::stod(std::string(in.begin(), in.size())), std::forward<Args&>(args)...);
        return true;
    }
};

template<typename Subclass>
struct FloatHelper : CRTP<Subclass>
{
    template<typename ParseInput, typename Data, typename... Args>
    static bool apply(ParseInput& in, Data& data, Args&... args)
    {
        CRTP<Subclass>::setValue(data, std::stof(std::string(in.begin(), in.size())), std::forward<Args&>(args)...);
        return true;
    }
};

template<typename T>
struct UTCAction
{
};

template<>
struct UTCAction<Utchh> : public IntHelper<UTCAction<Utchh>, uint8_t>
{
    static inline void setValue(UTC& utc, uint8_t value) { utc.hh = value; }
};

template<>
struct UTCAction<Utcmm> : public IntHelper<UTCAction<Utcmm>, uint8_t>
{
    static inline void setValue(UTC& utc, uint8_t value) { utc.mm = value; }
};

template<>
struct UTCAction<Utcss> : public IntHelper<UTCAction<Utcss>, uint8_t>
{
    static inline void setValue(UTC& utc, uint8_t value) { utc.ss = value; }
};

template<>
struct UTCAction<UtcssLast> : public IntHelper<UTCAction<UtcssLast>, uint8_t>
{
    static inline void setValue(UTC& utc, uint8_t value) { utc.ssLast = value; }
};

struct UTCHelperNoSetValue
{
    template<typename Rule,
             apply_mode A,
             rewind_mode M,
             template<typename...>
             class Action,
             template<typename...>
             class Control,
             typename ParseInput,
             typename Data,
             typename... States>
    static bool match(ParseInput& in, Data& data, States&&... st) // 具名要求 utc
    {
        try
        {
            if constexpr (std::is_same_v<std::decay_t<decltype(data.utc)>, std::optional<UTC>>)
            {
                UTC utc;
                if (tao::pegtl::match<Rule, A, M, UTCAction, Control>(in, utc))
                {
                    data.utc = utc;
                    return true;
                }
                else
                    return false;
            }
            else
                return tao::pegtl::match<Rule, A, M, UTCAction, Control>(in, data.utc);
        }
        catch (...)
        {
            return false;
        }
    }
};

template<typename Subclass>
using SystemIdHelper = IntHelper<Subclass, uint8_t, 16>;
template<typename Subclass>
using SignalIdHelper = SystemIdHelper<Subclass>;
template<typename Subclass>
using CheckSumHelper = SystemIdHelper<Subclass>;

template<typename Subclass, typename Enum>
struct EnumHelper : CRTP<Subclass>
{
    template<typename ParseInput, typename Data, typename... Args>
    static bool apply(ParseInput& in, Data& data, Args&... args)
    {
        Enum e;
        std::string name(in.begin(), in.size());
        if (!boost::describe::enum_from_string(name.c_str(), e))
            return false;
        CRTP<Subclass>::setValue(data, e, std::forward<Args&>(args)...);
        return true;
    }
};

template<typename Subclass>
using CRC32Helper = IntHelper<Subclass, uint32_t, 16>;

template<typename Subclass>
struct PositioningSystemModeIndicatorHelper : CRTP<Subclass>
{
    template<typename ParseInput, typename Data, typename... Args>
    static bool apply(ParseInput& in, Data& data, Args&... args)
    {
        PositioningSystemMode mode;
        std::string str(in.begin(), in.size());
        if (str == "A")
            mode = PositioningSystemMode::Autonomous;
        else if (str == "D")
            mode = PositioningSystemMode::Differential;
        else if (str == "E")
            mode = PositioningSystemMode::Estimated;
        else if (str == "M")
            mode = PositioningSystemMode::ManualInput;
        else if (str == "N")
            mode = PositioningSystemMode::DataNotValid;
        else
            return false;
        CRTP<Subclass>::setValue(data, mode, std::forward<Args&>(args)...);
        return true;
    }
};

template<typename T>
struct DateAction
{
};

template<>
struct DateAction<Datedd> : IntHelper<DateAction<Datedd>, uint8_t>
{
    static void setValue(Date& data, uint8_t value) { data.dd = value; }
};

template<>
struct DateAction<Datemm> : IntHelper<DateAction<Datemm>, uint8_t>
{
    static void setValue(Date& data, uint8_t value) { data.mm = value; }
};

template<>
struct DateAction<Dateyy> : IntHelper<DateAction<Dateyy>, uint8_t>
{
    static void setValue(Date& data, uint8_t value) { data.yy = value; }
};

template<typename Subclass>
struct DateExpressionHelper : CRTP<Subclass>
{
    template<typename ParseInput, typename Data, typename... Args>
    static bool apply(ParseInput& in, Data& data, Args&... args)
    {
        Date date;
        parse<DateExpression, DateAction>(string_input(std::string(in.begin(), in.size()), ""), date);
        CRTP<Subclass>::setValue(data, date, std::forward<Args&>(args)...);
        return true;
    }
};

template<typename Subclass>
struct StringHelper : CRTP<Subclass>
{
    template<typename Rule,
             apply_mode A,
             rewind_mode M,
             template<typename...>
             class Action,
             template<typename...>
             class Control,
             typename ParseInput,
             typename Data,
             typename... States>
    static bool match(ParseInput& in, Data& data, States&&... st)
    {
        try
        {
            std::string string;
            if (tao::pegtl::match<Rule, A, M, StringAction, Control>(in, string))
            {
                CRTP<Subclass>::setValue(data, string);
                return true;
            }
            return false;
        }
        catch (...)
        {
            return false;
        }
    }
};

template<typename Subclass>
struct ExtendedSolutionStatusHelper : CRTP<Subclass>
{
    union U
    {
        uint8_t raw;
        ExtendedSolutionStatus status;
    };
    template<typename ParseInput, typename Data, typename... Args>
    static bool apply(ParseInput& in, Data& data, Args&... args)
    {
        std::string str(in.begin(), in.size());
        U u;
        u.raw = static_cast<uint8_t>(std::stoi(str, nullptr, 16));
        CRTP<Subclass>::setValue(data, u.status, std::forward<Args&>(args)...);
        return true;
    }
};

template<typename Subclass>
struct BoolHelper : CRTP<Subclass>
{
    template<typename ParseInput, typename Data, typename... Args>
    static bool apply(ParseInput& in, Data& data, Args&... args)
    {
        std::string str(in.begin(), in.size());
        CRTP<Subclass>::setValue(data, str == "TRUE", std::forward<Args&>(args)...);
        return true;
    }
};
} // namespace Commons
} // namespace GnssLogs